local Aloft = Aloft
if not Aloft then return end

-----------------------------------------------------------------------------

local AloftCastBar = Aloft:NewModule("CastBar", Aloft, "AceEvent-3.0")
local SML = LibStub("LibSharedMedia-3.0")

-----------------------------------------------------------------------------

AloftCastBar.namespace = "castBar"
AloftCastBar.defaults =
{
	profile =
	{
		offsets =
		{
			left		= 0,
			right		= 0,
			vertical	= -12,
		},
		height			= 10,

		border			= "None",
		borderEdgeSize	= 16,
		borderInset		= 4,
		borderColor		= { 1, 1, 1, 1 },
		backdropColor	= { 0.25, 0.25, 0.25, 0.5 },
		color			= { 1.0, 0.7, 0.0, 1.0 },
		texture			= "Blizzard",

		nointerBorder			= "None",
		nointerBorderEdgeSize	= 16,
		nointerBorderInset		= 4,
		nointerBorderColor		= { 1, 1, 1, 1 },
		nointerBackdropColor	= { 0.25, 0.25, 0.25, 0.5 },
		nointerTexture			= "Blizzard",
		nointerColor			= { 1.0, 0.0, 0.0, 1.0 },
	},
}

-----------------------------------------------------------------------------

local inset
local castFrame

-- backdrop table for initializing visible nameplates
local backdropTable =
{
	-- tile = false,
	-- tileSize = 16,
	bgFile = nil,
	edgeSize = 16,
	edgeFile = nil,
	insets = { left = 0, right = 0, top = 0, bottom = 0 },
}

-- backdrop table for resetting nameplates when they are hidden
local defaultBackdropTable =
{
	-- tile = false,
	-- tileSize = 0,
	bgFile = nil,
	-- edgeSize = 0,
	edgeFile = nil,
	insets = { left = 0, right = 0, top = 0, bottom = 0 },
}

-----------------------------------------------------------------------------

function AloftCastBar:UpdateAll()
	-- nothing to do
end

-----------------------------------------------------------------------------

function AloftCastBar:OnInitialize()
	if self.db ~= Aloft.AloftDB:GetNamespace(self.namespace, true) then self.db = Aloft.AloftDB:RegisterNamespace(self.namespace, self.defaults) end
end

function AloftCastBar:OnEnable()
	self:UnregisterAllEvents()
	self:UnregisterAllMessages()
	self:CancelAllTimers()

	self:RegisterMessage("Aloft:SetupFrame", "SetupFrame")
	self:RegisterMessage("Aloft:OnNameplateHide", "OnNameplateHide")
	self:RegisterMessage("Aloft:OnCastBarShow", "OnCastBarShow")
	self:RegisterMessage("Aloft:OnCastBarHide", "OnCastBarHide")
	self:RegisterMessage("Aloft:OnCastBarValueChanged", "Update")
	-- self:RegisterEvent("CVAR_UPDATE", "OnCVarUpdate")

	self:RegisterMessage("SharedMedia_SetGlobal", function(message, mediatype, override)
		if mediatype == "statusbar" then
			self:UpdateAll()
		end
	end)

	self:RegisterMessage("Aloft:SetAll", function(message, type, value)
		if AloftCastBar.db.profile[type] then
			AloftCastBar.db.profile[type] = value

			-- special cases for "nointerrupt" side of things
			if type == "texture" then
				AloftCastBar.db.profile.nointerTexture = value
			elseif type == "border" then
				AloftCastBar.db.profile.nointerBorder = value
			end

			self:UpdateAll()
		end
	end)

	self:UpdateAll()
end

function AloftCastBar:OnDisable()
	self:UnregisterAllEvents()
	self:UnregisterAllMessages()
	self:CancelAllTimers()

	for aloftData in Aloft:IterateNameplates() do
		local nameplateFrame = aloftData.nameplateFrame
		local castBar = aloftData.castBar

		castBar:ClearAllPoints()
		castBar:SetPoint("BOTTOMRIGHT", aloftData.castBarOverlayRegion, "BOTTOMRIGHT", -4.5, 4.5)
		castBar:SetWidth(116.5)
		castBar:SetHeight(10.18)
		castBar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
		castBar:SetBackdrop(defaultBackdropTable)
		castBar:SetBackdropColor(0, 0, 0, 0)
		castBar:SetBackdropBorderColor(0, 0, 0, 0)

		castBar:SetFrameLevel(nameplateFrame:GetFrameLevel())
		if castFrame then castFrame:Hide() end
		-- castBar:Show()

		-- TODO: hide castFrame
	end
end

function AloftCastBar:IsDisplayEnabled()
	-- ChatFrame7:AddMessage("AloftCastBar:IsDisplayEnabled(): " .. tostring(type(GetCVar("ShowVKeyCastbar"))) .. "/" .. tostring(GetCVar("ShowVKeyCastbar")) .. "/" .. tostring(GetCVar("ShowVKeyCastbar") == "1"))
	return GetCVar("ShowVKeyCastbar") == "1"
end

-- function AloftCastBar:OnCVarUpdate(event, name, value)
	-- ChatFrame7:AddMessage("AloftCastBar:OnCVarUpdate(): " .. tostring(event) .. "/" .. tostring(name) .. "/" .. tostring(type(value)) .. "/" .. tostring(value))
-- end

-----------------------------------------------------------------------------

function AloftCastBar:GetCastFrame()
	return castFrame
end

function AloftCastBar:AcquireCastFrame(aloftData, noInterrupt)
	-- ChatFrame7:AddMessage("AloftCastBar:AcquireCastFrame(): acquire " .. tostring(aloftData.name) .. "/" .. tostring(noInterrupt))

	if not castFrame then
		castFrame = CreateFrame("Frame", nil, nil)
	end

	castFrame.noInterrupt = noInterrupt
	self:SetupFrame("AloftCastBar:AcquireCastFrame", aloftData)

	-- ChatFrame7:AddMessage("AloftCastBar:AcquireCastFrame(): " .. tostring(aloftData.name) .. "/" .. tostring(castFrame) .. "/" .. tostring(castFrame and castFrame.noInterrupt))
	return castFrame
end

function AloftCastBar:GetBorder(aloftData, noInterrupt)
	if noInterrupt then
		return ((self.db.profile.border ~= "None") and self.db.profile.nointerBorderInset) or 0, SML:Fetch("border", self.db.profile.nointerBorder)
		-- return 0, SML:Fetch("border", "None")
	else
		return ((self.db.profile.nointerBorder ~= "None") and self.db.profile.borderInset) or 0, SML:Fetch("border", self.db.profile.border)
		-- return 0, SML:Fetch("border", "None")
	end
end

function AloftCastBar:SetupFrame(message, aloftData)
	if castFrame then
		local texture, color, backdropColor, borderColor
		if castFrame.noInterrupt then
			texture = SML:Fetch("statusbar", self.db.profile.nointerTexture)
			color = self.db.profile.nointerColor
			backdropColor = self.db.profile.nointerBackdropColor
			borderColor = self.db.profile.nointerBorderColor
		else
			texture = SML:Fetch("statusbar", self.db.profile.texture)
			color = self.db.profile.color
			backdropColor = self.db.profile.backdropColor
			borderColor = self.db.profile.borderColor
		end
		local inset, edgeFile = self:GetBorder(aloftData, castFrame.noInterrupt)

		local castRegion = castFrame.castRegion
		if not castRegion then
			castRegion = castFrame:CreateTexture(nil, "ARTWORK")
			castRegion:SetBlendMode("BLEND")
			castFrame.castRegion = castRegion
		end

		castRegion:SetTexture(texture)
		castRegion:SetVertexColor(unpack(color))

		castRegion:ClearAllPoints()
		castRegion:SetPoint("TOPLEFT", castFrame, "TOPLEFT", inset, -inset)
		castRegion:SetPoint("BOTTOMLEFT", castFrame, "BOTTOMLEFT", inset, inset)
		castRegion:Show()
		-- ChatFrame7:AddMessage("AloftCastBar:SetupFrame(): " .. tostring(aloftData.name) .. "/" .. tostring(castRegion:GetTexture()))

		backdropTable.insets.left = inset
		backdropTable.insets.right = inset
		backdropTable.insets.top = inset
		backdropTable.insets.bottom = inset
		backdropTable.edgeFile = edgeFile
		backdropTable.edgeSize = (castFrame.noInterrupt and self.db.profile.nointerBorderEdgeSize) or self.db.profile.borderEdgeSize
		backdropTable.bgFile = texture

		castFrame:SetBackdrop(backdropTable)
		castFrame:SetBackdropBorderColor(unpack(borderColor))

		--[[
		local sr, sg, sb, sa = aloftData.castBarShieldRegion:GetVertexColor()
		ChatFrame7:AddMessage("AloftCastBar:UpdateCastBar(): " .. tostring(aloftData.name) .. "|"
			.. "|" .. tostring(sr) .. "-" .. tostring(sg) .. "-" .. tostring(sb) .. "-" .. tostring(sa)
				.. "/" .. floor(255*sr) .. "." .. floor(255*sg) .. "." .. floor(255*sb) .. "." .. floor(255*sa)
				.. "/" .. ("|c%02x%02x%02x%02xshield color|r"):format(floor(255*sa), floor(255*sr), floor(255*sg), floor(255*sb)))
		]]

		-- this manipulates the cast bar background always display above the frame background
		local _, _, _, _, backgroundRegion = castFrame:GetRegions()

		if backgroundRegion and backgroundRegion.SetBlendMode then
			-- NOTE: as the castFrame has regions added to it, for name text/time text, backgroundRegion will eventually appear in the correct region location
			-- TODO: kludgy, do something about this
			backgroundRegion:SetDrawLayer("BACKGROUND")
			backgroundRegion:SetBlendMode("BLEND")
			backgroundRegion:Show()
			-- ChatFrame7:AddMessage("AloftCastBar:SetupFrame(): background region " .. tostring(aloftData.name) .. "/" .. tostring(backgroundRegion:GetTexture()))
		else
			-- ChatFrame7:AddMessage("AloftCastBar:UpdateCastBar(): no background region " .. tostring(aloftData.name) .. "/" .. tostring(castFrame:GetNumRegions()))
		end

		if aloftData then
			local layoutFrame = aloftData.layoutFrame
			if not layoutFrame then
				layoutFrame = Aloft:AcquireLayoutFrame(aloftData)
			end
			if not layoutFrame then
				ChatFrame7:AddMessage("AloftCastBar:SetupFrame(): no layoutFrame " .. tostring(aloftData.name))
				ChatFrame7:AddMessage("AloftCastBar:SetupFrame(): " .. debugstack())
			end
			-- ChatFrame7:AddMessage("AloftCastBar:SetupFrame(): " .. tostring(aloftData.name) .. "/" .. tostring(castFrame))

			local nameplateFrame = aloftData.nameplateFrame
			castFrame:SetParent(nameplateFrame)
			castFrame:SetFrameLevel(nameplateFrame:GetFrameLevel())
			-- ChatFrame7:AddMessage("AloftCastBar:SetupFrame(): frame level " .. tostring(aloftData.name) .. "/" .. tostring(layoutFrame:GetFrameLevel()) .. "/" .. tostring(castFrame:GetFrameLevel()))

			-- ChatFrame7:AddMessage("AloftCastBar:SetupFrame(): cast bar frame level " .. tostring(aloftData.nameplateFrame:GetFrameLevel()))
			castFrame:ClearAllPoints()
			castFrame:SetPoint("TOPLEFT", layoutFrame, "TOPLEFT", self.db.profile.offsets.left - inset, self.db.profile.offsets.vertical + inset)
			castFrame:SetPoint("BOTTOMRIGHT", layoutFrame, "TOPRIGHT", self.db.profile.offsets.right + inset, self.db.profile.offsets.vertical - self.db.profile.height - inset)
			castFrame:Hide()
			-- NOTE: don't show the castFrame here; we don't show until the default cast bar values begin to change

			local r, g, b, a = unpack(backdropColor)
			local _, _, _, castBarAlpha = aloftData.castBar:GetStatusBarColor()
			a = a * castBarAlpha

			castFrame:SetBackdropColor(r, g, b, a)
		end
	end
end

function AloftCastBar:ReleaseCastFrame(aloftData)
	-- ChatFrame7:AddMessage("AloftCastBar:ReleaseCastFrame(): release " .. aloftData.name)
	self:CleanupCastFrame(aloftData)
	self:RePoolCastFrame(aloftData)
end

function AloftCastBar:CleanupCastFrame(aloftData)
	if castFrame then
		castFrame.noInterrupt = nil
		-- if castFrame.castRegion then castFrame.castRegion:Hide() end
		-- ChatFrame7:AddMessage("AloftCastBar:CleanupCastFrame(): hide")
		self:ClearBackdrop(castFrame)
		castFrame:Hide()
	end
end

function AloftCastBar:RePoolCastFrame(aloftData)
	if castFrame then
		-- ChatFrame7:AddMessage("AloftCastBar:RePoolCastFrame(): " .. tostring(aloftData.name) .. "/" .. tostring(castFrame))
		castFrame:SetParent(nil) -- disassociate from nameplate when cast is done
	end
end

function AloftCastBar:ClearBackdrop(castFrame)
	if castFrame then
		castFrame:SetBackdropColor(0, 0, 0, 0)
		castFrame:SetBackdropBorderColor(0, 0, 0, 0)

		-- NOTE: doing this on nameplate hide may case #132 crashes
		-- castFrame:SetBackdrop(defaultBackdropTable)
	end
end

-----------------------------------------------------------------------------

function AloftCastBar:OnNameplateHide(message, aloftData)
	if aloftData.isTarget or aloftData:IsTarget() then
		self:ReleaseCastFrame(aloftData)
	end
end

function AloftCastBar:OnCastBarShow(message, aloftData)
	-- ChatFrame7:AddMessage("AloftCastBar:OnCastBarShow(): enter " .. tostring(aloftData.name))

	if self:IsDisplayEnabled() then
		local name, noInterrupt = self:GetTargetSpellInfo()
		-- ChatFrame7:AddMessage("AloftCastBar:OnCastBarShow(): acquire " .. tostring(aloftData.name) .. "/" .. tostring(name) .. "/" .. tostring(noInterrupt))
		if name then
			self:AcquireCastFrame(aloftData, noInterrupt)
			aloftData.castBar:Hide()
			-- ChatFrame7:AddMessage("AloftCastBar:OnCastBarShow(): show " .. tostring(aloftData.name) .. "/" .. tostring(name) .. "/" .. tostring(noInterrupt) .. "/" .. tostring(castFrame:GetFrameLevel()))
			self:SendMessage("Aloft:OnCastFrameShow", aloftData)
			return
		end
	else
		-- ChatFrame7:AddMessage("AloftCastBar:OnCastBarShow(): disabled")
	end

	self:ReleaseCastFrame(aloftData)
	self:SendMessage("Aloft:OnCastFrameHide", aloftData)
end

function AloftCastBar:OnCastBarHide(message, aloftData)
	-- ChatFrame7:AddMessage("AloftCastBar:OnCastBarHide(): enter " .. tostring(aloftData.name))
	-- if castFrame then
	--	castFrame:Hide()
	-- end
end

-----------------------------------------------------------------------------

function AloftCastBar:CalculateFrameWidth(aloftData, castFrame)
	local inset, _ = self:GetBorder(aloftData, castFrame.noInterrupt)

	-- by hook or by crook, value must end up non-nil
	local castBar = aloftData.castBar
	local value = castBar:GetValue()
	local minValue, maxValue = castBar:GetMinMaxValues()
	local width = (value / maxValue) * (castFrame:GetWidth() - (2 * inset))

	return minValue, value, maxValue, width
end

function AloftCastBar:Update(message, aloftData)
	if self:IsDisplayEnabled() and castFrame and aloftData.nameplateFrame:IsVisible() and (not aloftData.alphaOverride or aloftData.alphaOverride > 0) then
		local minValue, value, maxValue, width = self:CalculateFrameWidth(aloftData, castFrame)
		if (value > minValue) and (value < maxValue) then
			local castRegion = castFrame.castRegion
			if width and castRegion then
				-- ChatFrame7:AddMessage("AloftCastBar:Update(): " .. tostring(aloftData.name) .. "/" .. tostring(value) .. "/" .. tostring(width))
				castRegion:SetWidth(width)
				castFrame:Show() -- show on every value change
				-- aloftData.castBar:Hide()
				return
			end
		end
	else
		-- ChatFrame7:AddMessage("AloftCastBar:Update(): disabled")
	end

	self:ReleaseCastFrame(aloftData)
	self:SendMessage("Aloft:OnCastFrameHide", aloftData) -- for some reason, this works much better here (frame update delay issues?)
end

-----------------------------------------------------------------------------

function AloftCastBar:GetTargetSpellInfo()
	local _, _, displayName, _, _, _, _, castId, noInterrupt = UnitCastingInfo("target")
	if displayName then
		-- ChatFrame7:AddMessage("AloftCastBar:GetTargetSpellInfo(): casting " .. tostring(UnitName("target")) .. "/" .. tostring(displayName) .. "/" .. tostring(castId) .. "/" .. tostring(noInterrupt))
		return displayName, noInterrupt
	end
	_, _, displayName, _, _, _, _, noInterrupt = UnitChannelInfo("target")
	if displayName then
		-- ChatFrame7:AddMessage("AloftCastBar:GetTargetSpellInfo(): channel " .. tostring(UnitName("target")) .. "/" .. tostring(displayName) .. "/" .. tostring(castId) .. "/" .. tostring(noInterrupt))
		return displayName, noInterrupt
	end
	return nil, nil
end

-----------------------------------------------------------------------------
